﻿using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using UtilZ.Dotnet.Ex.Base;
using UtilZ.Dotnet.Ex.Communication.Base;
using UtilZ.Dotnet.Ex.Exceptions;
using UtilZ.Dotnet.Ex.Model;
using UtilZ.Dotnet.Ex.ZLog;

namespace UtilZ.Dotnet.Ex.Communication.Ports
{
    /// <summary>
    /// 串口通信客户端
    /// </summary>
    public class SerialPortClient : ClientAbs
    {
        private readonly static ITimestampFactory _sendDataIntervalTimestampFactory;
        static SerialPortClient()
        {
            _sendDataIntervalTimestampFactory = new TicksTimestampFactory(new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Local), TimestampUnit.Nanosecond100);
        }

        private static ISerialPortFactory _factory = null;
        /// <summary>
        /// 获取或设置串口工厂类实例(可以显示指定,也可不指定,建议显示指定).
        /// 不显示设置的话,则会隐式的通过在当前应用程序域中的程序集中查找可用的串口工厂类,找到一个就结束,如果应用程序域中存在多个工厂类,则可能不是所需要的类型.
        /// </summary>
        public static ISerialPortFactory Factory
        {
            get
            {
                if (_factory == null)
                {
                    Type iSerialPortFactoryType = typeof(ISerialPortFactory);
                    Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
                    foreach (var assembly in assemblies)
                    {
                        Type[] types = assembly.GetTypes();
                        foreach (var type in types)
                        {
                            if (!type.IsClass ||
                                type.IsAbstract ||
                                type.GetInterface(iSerialPortFactoryType.FullName) == null ||
                                !type.GetConstructors().Where(c => { return c.GetParameters().Length == 0; }).Any())
                            {
                                //不是类或抽象类或没有实现IDBInteraction接口或没有无参构造函数,忽略
                                continue;
                            }

                            try
                            {
                                _factory = (ISerialPortFactory)Activator.CreateInstance(type);
                                break;
                            }
                            catch (Exception ex)
                            {
                                ZLog.ZLoger.Warn(ex, $"使用类型\"{type.FullName}\"自动创建串口工厂实例发生异常");
                            }
                        }
                    }
                }

                return _factory;
            }
            set
            {
                _factory = value;
            }
        }




        private readonly SerialPortClientInitPara _serialPortInitPara;
        /// <summary>
        /// 获取串口信息
        /// </summary>
        public SerialPortClientInitPara SerialPortInitPara
        {
            get { return this._serialPortInitPara; }
        }


        private ISerialPort _serialPort = null;

        /// <summary>
        /// 表示将处理 System.IO.Ports.SerialPort 对象的串行管脚更改事件的方法
        /// </summary>
        [MonitoringDescription("SerialPinChanged")]
        public event EventHandler<EventArgs<SerialPortPinChange>> PinChanged;

        /// <summary>
        /// 连接改变事件
        /// </summary>
        [MonitoringDescription("连接改变事件")]
        public event EventHandler<SerialPortConnectionChangedArgs> ConnectChanged;
        /// <summary>
        /// 触发连接改变事件
        /// </summary>
        /// <param name="status">连接状态</param>
        /// <param name="disconnectMode">断开连接模式</param>
        public void OnRaiseConnectChanged(ConnectionStatus status, DisconnectMode disconnectMode)
        {
            if (status == ConnectionStatus.Opened)
            {
                this._connected = true;
            }
            else
            {
                this._connected = false;
            }

            var handler = this.ConnectChanged;
            if (handler != null)
            {
                handler(this, new SerialPortConnectionChangedArgs(status, disconnectMode, this._serialPortInitPara.PortName));
            }
        }

        private bool _connected = false;
        /// <summary>
        /// 当前串口是否已打开(已连接)
        /// </summary>
        public override bool Connected
        {
            get
            {
                ISerialPort serialPort = this._serialPort;
                if (serialPort == null)
                {
                    return false;
                }

                return this._connected && serialPort.IsOpen;
            }
        }

        /// <summary>
        /// 异步打开串口线程
        /// </summary>
        private ThreadEx _asynOpenThread = null;

        /// <summary>
        /// 该是否已释放[true:已释放;false:未已释放]
        /// </summary>
        private bool _disposabled = false;


        private readonly SerialPortUnpackerPara _unpackerPara;
        private byte[] _buffer = null;
        private MemoryStream _memoryStream;
        private BinaryReader _reader = null;
        private int _dataLength = 0;
        private int _bufferReadOffset = 0;
        private int _bufferWirteOffset = 0;



        private readonly object _sendIntervalLock = new object();
        private readonly long _sendIntervalMilliseconds = 0;
        private long _lastSendDataTimestamp = 0;
        private readonly CancellationTokenEx _sendDataIntervalToken = null;

        private readonly bool _enableSendHeartData = false;

        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="initPara">串口信息</param>
        public SerialPortClient(SerialPortClientInitPara initPara)
            : base(initPara)
        {
            if (initPara == null)
            {
                throw new ArgumentNullException(nameof(initPara));
            }

            this._serialPortInitPara = initPara;

            this._unpackerPara = new SerialPortUnpackerPara()
            {
                Client = this,
                LinkInfo = new SerialPortLinkInfo(initPara.PortName),
            };
            this.ExtendBuffer(initPara.ReceiveBufferSize);

            this._sendIntervalMilliseconds = initPara.SendIntervaMilliseconds;
            if (this._sendIntervalMilliseconds > 0)
            {
                this._sendDataIntervalToken = new CancellationTokenEx();
            }

            if (initPara.ClientHeart != null)
            {
                ClientHeartManager.Instance.Add(this, initPara.ClientHeart);
                this._enableSendHeartData = true;
            }
        }


        private void ExtendBuffer(int length)
        {
            byte[] buffer = new byte[length];
            if (this._buffer != null && this._dataLength > 0)
            {
                Array.Copy(this._buffer, this._bufferReadOffset, buffer, 0, this._dataLength);
            }
            this._bufferReadOffset = 0;
            this._bufferWirteOffset = this._dataLength;

            this._buffer = buffer;
            this._memoryStream = new MemoryStream(this._buffer);
            this._reader = new BinaryReader(this._memoryStream);
            this._unpackerPara.Buffer = this._buffer;
            this._unpackerPara.Reader = this._reader;
        }




        /// <summary>
        /// 关闭串口
        /// </summary>
        public void Close()
        {
            lock (base._disposeLock)
            {
                if (this._disposabled)
                {
                    throw new ObjectDisposedException(nameof(SerialPortClient));
                }

                lock (this._serialPortOpenLock)
                {
                    if (this._serialPort == null)
                    {
                        return;
                    }

                    this.ReleaSerialPort();
                }
            }

            this.OnRaiseConnectChanged(ConnectionStatus.Closed, DisconnectMode.Active);
        }

        /// <summary>
        /// 同步建立串口通信连接线程
        /// </summary>
        public void Open()
        {
            lock (base._disposeLock)
            {
                if (this._disposabled)
                {
                    throw new ObjectDisposedException(nameof(SerialPortClient));
                }

                this.PrimitiveOpenSerialPort();
            }
        }

        /// <summary>
        /// 异步建立串口通信连接线程
        /// </summary>
        public void AsyncOpen()
        {
            lock (base._disposeLock)
            {
                if (this._disposabled)
                {
                    throw new ObjectDisposedException(nameof(SerialPortClient));
                }

                if (this._asynOpenThread == null)
                {
                    this._asynOpenThread = new ThreadEx(this.AsynOpenThreadMethod, $"串口{this._serialPortInitPara.PortName}异步连接线程", true);
                }
                this._asynOpenThread.Start();
            }
        }
        private void AsynOpenThreadMethod(ThreadPara threadPara)
        {
            try
            {
                int asynOpenIntervalMilliseconds = this._serialPortInitPara.AsynOpenIntervalMilliseconds;
                while (!threadPara.Token.IsCancellationRequested)
                {
                    try
                    {
                        lock (base._disposeLock)
                        {
                            if (base._disposed)
                            {
                                break;
                            }

                            this.PrimitiveOpenSerialPort();
                        }
                        break;
                    }
                    catch (Exception exi)
                    {
                        ZLoger.Error(exi, "创建并打开指定的串口异常");
                    }

                    if (threadPara.WaitOne(asynOpenIntervalMilliseconds))
                    {
                        continue;
                    }
                }
            }
            catch (Exception ex)
            {
                ZLoger.Error(ex);
            }
        }




        private readonly object _serialPortOpenLock = new object();
        private void PrimitiveOpenSerialPort()
        {
            ISerialPortFactory factory = _factory;
            if (factory == null)
            {
                throw new ConnectException($"串口客户端类\"{nameof(SerialPortClient)}\"中需要先设置\"{nameof(SerialPortClient)}.{nameof(Factory)}\"实例,该实例必须实现接口\"{typeof(ISerialPortFactory).FullName}\".");
            }

            string[] portNames = factory.GetPortNames();
            if (portNames.Length == 0)
            {
                throw new ConnectException("没有可用的串口");
            }

            if (!portNames.Contains(this._serialPortInitPara.PortName))
            {
                throw new ArgumentException($"名为\"{this._serialPortInitPara.PortName}\"的串口不可用,可用的串口包括:\"{string.Join(",", portNames)}\"");
            }

            lock (this._serialPortOpenLock)
            {
                if (this._serialPort != null && this._serialPort.IsOpen)
                {
                    return;
                }

                ISerialPort serialPort = null;
                try
                {
                    this.ReleaSerialPort();

                    //重置接收缓存数据长度/读写位置
                    this._dataLength = 0;
                    this._bufferReadOffset = 0;
                    this._bufferWirteOffset = 0;

                    serialPort = factory.CreateSerialPort(this._serialPortInitPara);
                    serialPort.DataReceived += this.SerialPort_DataReceived;
                    serialPort.ErrorReceived += this.SerialPort_ErrorReceived;
                    serialPort.PinChanged += this.SerialPort_PinChanged;
                    serialPort.Open();
                    this._bufferReadOffset = 0;
                    this._bufferWirteOffset = 0;
                    this._dataLength = 0;
                    this._serialPort = serialPort;
                }
                catch (Exception ex)
                {
                    if (serialPort != null)
                    {
                        serialPort.DataReceived -= this.SerialPort_DataReceived;
                        serialPort.ErrorReceived -= this.SerialPort_ErrorReceived;
                        serialPort.PinChanged -= this.SerialPort_PinChanged;
                        serialPort.Dispose();
                    }

                    throw new ConnectException("创建并打开指定的串口异常", ex);
                }
            }

            this.OnRaiseConnectChanged(ConnectionStatus.Opened, DisconnectMode.Active);
        }




        private void SerialPort_PinChanged(object sender, EventArgs<SerialPortPinChange> e)
        {
            try
            {
                this.PinChanged?.Invoke(sender, e);
            }
            catch (Exception ex)
            {
                ZLoger.Error(ex, "处理串口PinChanged事件时发生异常");
            }
        }

        private void SerialPort_ErrorReceived(object sender, EventArgs<SerialPortError> e)
        {
            try
            {
                lock (this._serialPortOpenLock)
                {
                    this.ReleaSerialPort();
                }

                this.OnRaiseConnectChanged(ConnectionStatus.Closed, DisconnectMode.Passive);
                this.AsyncOpen();
            }
            catch (Exception ex)
            {
                ZLoger.Error(ex, "处理串口接收数据错误时发生异常");
            }
        }

        private void SerialPort_DataReceived(object sender, EventArgs<SerialPortData> e)
        {
            try
            {
                if (this._disposabled)
                {
                    return;
                }

                var serialPort = (ISerialPort)sender;
                int revLen = serialPort.BytesToRead;

                if (revLen > 0)
                {
                    base.UpdateLastReceiveDataTimestamp();
                    int reqLen = this._dataLength + revLen;
                    if (this._buffer == null || this._buffer.Length < reqLen)
                    {
                        //扩容
                        int extendLen;
                        if (reqLen > 4096)
                        {
                            extendLen = (int)(reqLen * 1.5f);//大于了4k后,每次扩容为原来的1.5倍
                        }
                        else
                        {
                            extendLen = reqLen * 2;//小于4k时,每次扩容为原来的2倍
                        }

                        //扩容
                        this.ExtendBuffer(extendLen);
                    }
                    else
                    {
                        if (this._buffer.Length - this._bufferWirteOffset < revLen)
                        {
                            //buffer中剩余的空间不够,将数据移动到起始位置,腾出足够的空间接收数据
                            Array.Copy(this._buffer, this._bufferReadOffset, this._buffer, 0, this._dataLength);
                            this._bufferReadOffset = 0;
                            this._bufferWirteOffset = this._dataLength;
                        }
                    }

                    //从串口读取数据
                    int revLen2 = revLen, readByteCount;
                    while (revLen2 > 0)
                    {
                        readByteCount = serialPort.Read(this._buffer, this._bufferWirteOffset, revLen2);
                        this._dataLength += readByteCount;
                        this._bufferWirteOffset += readByteCount;
                        revLen2 -= readByteCount;
                    }
                }

                this._unpackerPara.EventType = e.Para;
                this._unpackerPara.Offset = this._bufferReadOffset;
                this._memoryStream.Position = this._bufferReadOffset;
                this._unpackerPara.SerialPort = serialPort;
                this._unpackerPara.ReceiveLength = revLen;
                this._unpackerPara.DataLength = this._dataLength;

                int unpackgeLen = base._initPara.Unpacker.UnPackge(this._unpackerPara);
                if (unpackgeLen > 0)
                {
                    this._bufferReadOffset += unpackgeLen;
                    this._dataLength -= unpackgeLen;
                }
            }
            catch (Exception ex)
            {
                ZLoger.Error(ex, "读取串口接收到的数据时发生异常");
            }
        }















        #region 发送数据

        /// <summary>
        /// 发送数据
        /// </summary>
        /// <param name="bufferData">要发送的数据对象</param>
        /// <param name="options">发送选项</param>
        protected override void PrimitiveSend(DataSource bufferData, ISendOptions options)
        {
            if (bufferData == null)
            {
                return;
            }

            if (bufferData.DataType == DataSourceType.Bytes)
            {
                this.Send(bufferData.Bytes, bufferData.Offset, bufferData.Length);
            }
            else
            {
                throw new NotImplementedException($"串口发送数据类型\"{bufferData.DataType.ToString()}\"未实现");
            }
        }

        private ISerialPort GetSerialPort()
        {
            lock (base._disposeLock)
            {
                if (this._disposabled)
                {
                    throw new ObjectDisposedException(this.ToString());
                }

                ISerialPort serialPort = this._serialPort;
                if (serialPort == null)
                {
                    this.PrimitiveOpenSerialPort();
                    serialPort = this._serialPort;
                }

                return serialPort;
            }
        }

        /// <summary>
        /// 将指定的字符串写入串行端口
        /// 异常:
        /// T:System.InvalidOperationException:指定的端口未打开。
        /// T:System.ServiceProcess.TimeoutException:该操作未在超时时间到期之前完成。
        /// </summary>
        /// <param name="text">输出字符串</param>
        public void Send(string text)
        {
            if (string.IsNullOrEmpty(text))
            {
                return;
            }


            if (this._sendIntervalMilliseconds > 0)
            {
                this.WaitSendInterval();
            }

            this.GetSerialPort().Send(text);
            base.UpdateLastSendDataTimestamp();
        }

        /// <summary>
        /// 使用缓冲区的数据将指定数量的字节写入串行端口
        /// 异常:
        /// T:System.InvalidOperationException:指定的端口未打开。
        /// T:System.ArgumentOutOfRangeException:offset 或 count 参数超出了所传递的 buffer 的有效区域。offset 或 count 小于零。
        /// T:System.ArgumentException:offset 加上 count 大于 buffer 的长度。
        /// T:System.ServiceProcess.TimeoutException:该操作未在超时时间到期之前完成。
        /// </summary>
        /// <param name="buffer">包含要写入端口的数据的字节数组</param>
        /// <param name="offset">buffer 参数中从零开始的字节偏移量，从此处开始将字节复制到端口</param>
        /// <param name="size">要写入的字节数</param>
        public void Send(byte[] buffer, int offset, int size)
        {
            if (buffer == null || buffer.Length == 0)
            {
                return;
            }

            if (this._sendIntervalMilliseconds > 0)
            {
                this.WaitSendInterval();
            }

            this.GetSerialPort().Send(buffer, offset, size);
            base.UpdateLastSendDataTimestamp();
        }

        /// <summary>
        /// 使用缓冲区的数据将指定数量的字符写入串行端口
        /// 异常:
        /// T:System.InvalidOperationException:指定的端口未打开。
        /// T:System.ArgumentOutOfRangeException: offset 或 count 参数超出了所传递的 buffer 的有效区域。offset 或 count 小于零。
        /// T:System.ArgumentException:offset 加上 count 大于 buffer 的长度。
        /// T:System.ServiceProcess.TimeoutException:该操作未在超时时间到期之前完成。
        /// </summary>
        /// <param name="buffer">包含要写入端口的数据的字符数组</param>
        /// <param name="offset">buffer 参数中从零开始的字节偏移量，从此处开始将字节复制到端口</param>
        /// <param name="size">要写入的字符数</param>
        public void Send(char[] buffer, int offset, int size)
        {
            if (buffer == null || buffer.Length == 0)
            {
                return;
            }

            if (this._sendIntervalMilliseconds > 0)
            {
                this.WaitSendInterval();
            }

            this.GetSerialPort().Send(buffer, offset, size);
            base.UpdateLastSendDataTimestamp();
        }

        /// <summary>
        /// 将指定的字符串和 System.IO.Ports.SerialPort.NewLine 值写入输出缓冲区
        /// 异常:
        /// T:System.InvalidOperationException:指定的端口未打开。
        /// T:System.TimeoutException:System.IO.Ports.SerialPort.WriteLine(System.String) 方法未能写入流。
        /// </summary>
        /// <param name="text">写入输出缓冲区的字符串</param>
        public void SendLine(string text)
        {
            if (string.IsNullOrEmpty(text))
            {
                return;
            }

            if (this._sendIntervalMilliseconds > 0)
            {
                this.WaitSendInterval();
            }

            this.GetSerialPort().Send(text);
            base.UpdateLastSendDataTimestamp();
        }

        /// <summary>
        /// 等待发送间隔[返回true,有等待发送间隔;返回false无等待]
        /// </summary>
        /// <returns>返回true,有等待发送间隔;返回false无等待</returns>
        private void WaitSendInterval()
        {
            lock (this._sendIntervalLock)
            {
                var modMilliseconds = _sendDataIntervalTimestampFactory.GetTimestamp() - this._lastSendDataTimestamp;
                if (modMilliseconds < this._sendIntervalMilliseconds)
                {
                    //两次之间的发送间隔小于了最小值，则等待差值毫秒
                    if (this._sendDataIntervalToken.WaitOne((int)(this._sendIntervalMilliseconds - modMilliseconds)))
                    {
                        //资源已释放,取消发送
                        if (this._disposed)
                        {
                            throw new ObjectDisposedException(this.ToString());
                        }
                    }
                }
            }
        }
        #endregion


        /// <summary>
        /// 
        /// </summary>
        /// <param name="disposing"></param>
        protected override void Dispose(bool disposing)
        {
            try
            {
                if (this._enableSendHeartData)
                {
                    ClientHeartManager.Instance.Remove(this);
                }

                lock (this._serialPortOpenLock)
                {
                    this.ReleaSerialPort();
                }

                if (this._asynOpenThread != null)
                {
                    this._asynOpenThread.Dispose();
                    this._asynOpenThread = null;
                }

                if (this._sendDataIntervalToken != null)
                {
                    this._sendDataIntervalToken.Dispose();
                }

                if (this._memoryStream != null)
                {
                    this._memoryStream.Dispose();
                    this._memoryStream = null;
                    this._buffer = null;
                }
            }
            catch (Exception ex)
            {
                ZLoger.Error(ex);
            }
        }

        private void ReleaSerialPort()
        {
            try
            {
                if (this._asynOpenThread != null)
                {
                    this._asynOpenThread.Dispose();
                    this._asynOpenThread = null;
                }

                ISerialPort serialPort = this._serialPort;
                if (serialPort == null)
                {
                    return;
                }

                serialPort.DataReceived -= this.SerialPort_DataReceived;
                serialPort.ErrorReceived -= this.SerialPort_ErrorReceived;
                serialPort.PinChanged -= this.SerialPort_PinChanged;
                if (serialPort.IsOpen)
                {
                    serialPort.Close();
                }
                serialPort.Dispose();
                this._serialPort = null;
            }
            catch (Exception ex)
            {
                ZLoger.Error(ex);
            }
        }

    }
}
